# ~*~ coding: utf-8 ~*~
import os
import time
import yaml
from flask import current_app
from flask import request, views, render_template, jsonify
from flask_login import current_user

from apps.common.utils import fill_date_to_timestamp, timestamp_obj_to_str, get_remote_address, generate_uuid
from apps.common.mixin import CompatibleWithSelect2Mixin
from apps.common import response
from apps.extensions import db
from apps.perms.mixin import PermissionRequiredMixin, LoginRequiredMixin
from apps.tasks.models import InventoryGroup
from apps.assets.models import Asset
from ...models import PlayBook, PlaybookExecuteLog
from ...forms import PlayBookForm, PlayBookExecuteForm
from ...tasks import run_playbook
from ...utils import yaml_host_replace


__all__ = [
    'PlayBookListAPI', 'PlayBookListView', 'PlayBookCreateView',
    'PlayBookUpdateView', 'PlayBookDeleteView', 'PlayBookExecuteView',
    'PlayBookExecuteLogAPI', 'PlayBookExecuteLogView', 'PlayBookFileView'
]


class PlayBookListAPI(CompatibleWithSelect2Mixin, PermissionRequiredMixin, LoginRequiredMixin, views.MethodView):
    permission_required = 'tasks-playbook-list'

    def get(self):
        create_time = request.args.get('create_time')
        start_time = end_time = None
        if create_time:
            start_time = fill_date_to_timestamp('start', create_time.split('~')[0])
            end_time = fill_date_to_timestamp('end', create_time.split('~')[1])

        filter_conditions = {
            'and': [
                ('create_time', 'gte', start_time),
                ('create_time', 'lte', end_time),
            ],
            'or': [
                # ('name', 'like', request.args.get('search')),
            ],
        }

        # 排序
        sort = request.args.get('sort') or 'create_time'
        order = request.args.get('order') or 'desc'
        order_by_list = [(sort, order), ]

        # 分页
        offset = request.args.get('offset') or 0
        limit = request.args.get('limit') or 12

        # Compatible with select2
        if request.values.get('type') == 'select2':
            filter_conditions, order_by_list, offset, limit = self.select2_queryset()

        playbooks, count = PlayBook.get_queryset(filter_conditions=filter_conditions,
                                                 order_by_list=order_by_list,
                                                 offset=offset, limit=limit)
        results = list()
        for playbook in playbooks:
            results.append({
                'id': playbook.id,
                'name': playbook.name,
                'content': playbook.content,
                'desc': playbook.desc,
                'create_by': playbook.create_by,
                'create_time': timestamp_obj_to_str(playbook.create_time),
            })

        resp = {
            "data": {
                "rows": results,
                "total": count
            }
        }
        return jsonify(resp)


class PlayBookListView(PermissionRequiredMixin, LoginRequiredMixin, views.MethodView):
    permission_required = 'tasks-playbook-list'

    def get(self):
        return render_template('tasks/realtime/playbook_list.html')


class PlayBookUpdateView(PermissionRequiredMixin, LoginRequiredMixin, views.MethodView):
    permission_required = 'tasks-playbook-update'

    def get(self, pk):
        instance = db.session.query(PlayBook).get(pk)
        content = {
            'id': instance.id,
            'name': instance.name,
            'content': instance.content,
            'desc': instance.desc,
            'create_by': instance.create_by,
            'create_time': timestamp_obj_to_str(instance.create_time),
        }
        return render_template('tasks/realtime/playbook_update.html', **content)

    def post(self, pk):
        form = PlayBookForm(request.values)
        instance = db.session.query(PlayBook).get(pk)
        if form.validate():
            _clean_data = form.data
            if instance.name != _clean_data.get('name'):
                if db.session.query(PlayBook).filter_by(name=_clean_data.get('name')).count():
                    return response.params_error({
                        'field': 'name',
                        'message': '模块名称不能重复'
                    })
            instance.update(**_clean_data)
            return response.success()
        else:
            return response.params_error(form.get_error())


class PlayBookCreateView(PermissionRequiredMixin, LoginRequiredMixin, views.MethodView):
    permission_required = 'tasks-playbook-create'

    def get(self):
        return render_template('tasks/realtime/playbook_create.html')

    def post(self):
        form = PlayBookForm(request.values)
        if form.validate():
            _clean_data = form.data
            if db.session.query(PlayBook).filter_by(name=_clean_data.get('name')).count():
                return response.params_error({
                    'field': 'name',
                    'message': '模块名称不能重复'
                })
            _clean_data['create_by'] = current_user.name
            PlayBook.create(**_clean_data)
            return response.success()
        else:
            return response.params_error(form.get_error())


class PlayBookDeleteView(PermissionRequiredMixin, LoginRequiredMixin, views.MethodView):
    permission_required = 'tasks-playbook-delete'

    def post(self, pk):
        instance = db.session.query(PlayBook).get(pk)
        instance.delete()
        return response.success()


class PlayBookExecuteView(PermissionRequiredMixin, LoginRequiredMixin, views.MethodView):
    permission_required = 'tasks-playbook-execute'

    def get(self):
        pk = request.args.get('pk')
        instance = db.session.query(PlayBook).get(pk)
        content = {
            'id': instance.id,
            'name': instance.name,
            'content': instance.content,
        }
        return render_template('tasks/realtime/playbook_execute.html', **content)

    def post(self):
        form = PlayBookExecuteForm(request.values)
        if form.validate():
            _clean_data = form.data

            content = _clean_data.get('content')
            host_ids = _clean_data.get('hosts').split(',')
            group_ids = _clean_data.get('groups').split(',')
            groups = db.session.query(InventoryGroup).filter(InventoryGroup.id.in_(group_ids)).all()
            hosts = db.session.query(Asset).filter(Asset.id.in_(host_ids)).all()
            host_names = [host.hostname for host in hosts]
            group_names = [group.name for group in groups]

            host_list = []
            host_list.extend(host_names)
            host_list.extend(group_names)
            exec_name = '_exec_playbook_{}.yml'.format(generate_uuid())
            playbook_path = os.path.join(current_app.config.get('TASKS_BASE').rstrip('/'), exec_name)

            content = content.replace("${hosts}", ",".join(host_list))
            with open(playbook_path, 'w+', encoding='utf-8') as f:
                f.write(content)
                os.chmod(playbook_path, 0o400)

            # log recode
            host_names = [host.hostname for host in hosts]
            group_names = [group.name for group in groups]
            log = {
                'user': "{} ({})".format(current_user.name, current_user.username),
                'remote_ip': get_remote_address(),
                'playbook': content,
                'hosts': ','.join(host_names),
                'groups': ','.join(group_names),
                'exec_time': time.time(),
            }
            log = PlaybookExecuteLog.create(**log)

            # run palybook
            task = run_playbook.apply_async(args=(hosts, groups, playbook_path, log), serializer='pickle')
            log.update(celery_id=task.id)
            return response.success(data={'task_id': task.id})
        else:
            return response.params_error(form.get_error())


class PlayBookExecuteLogAPI(PermissionRequiredMixin, LoginRequiredMixin, views.MethodView):
    permission_required = 'tasks-log-playbook'

    def get(self):
        create_time = request.args.get('create_time')
        start_time = end_time = None
        if create_time:
            start_time = fill_date_to_timestamp('start', create_time.split('~')[0])
            end_time = fill_date_to_timestamp('end', create_time.split('~')[1])

        filter_conditions = {
            'and': [
                ('exec_time', 'gte', start_time),
                ('exec_time', 'lte', end_time),
            ],
            'or': [
                ('name', 'like', request.args.get('search')),
            ],
        }

        # 排序
        sort = request.args.get('sort') or 'create_time'
        order = request.args.get('order') or 'desc'
        order_by_list = [(sort, order), ]

        # 分页
        offset = request.args.get('offset') or 0
        limit = request.args.get('limit') or 12

        logs, count = PlaybookExecuteLog.get_queryset(filter_conditions=filter_conditions,
                                                      order_by_list=order_by_list,
                                                      offset=offset, limit=limit)
        results = list()
        for log in logs:
            playbook = []
            res = yaml.safe_load(log.playbook)
            for r in res:
                for t in r['tasks']:
                    playbook.append(t['name'])

            result = 'unknown'
            if log.result:
                result = True
                stats = log.result['stats']
                for key, val in stats.items():
                    if val['failures'] != 0:
                        result = False
                        break

            results.append({
                'id': log.id,
                'celery_id': log.celery_id,
                'user': log.user,
                'remote_ip': log.remote_ip,
                'playbook': '<br>'.join(playbook),
                'hosts': log.hosts,
                'groups': log.groups,
                'result': result,
                'exec_time': timestamp_obj_to_str(log.exec_time),
            })

        resp = {
            "data": {
                "rows": results,
                "total": count
            }
        }
        return jsonify(resp)


class PlayBookExecuteLogView(PermissionRequiredMixin, LoginRequiredMixin, views.MethodView):
    permission_required = 'tasks-log-playbook'

    def get(self):
        return render_template('tasks/realtime/playbook_log.html')


class PlayBookFileView(PermissionRequiredMixin, LoginRequiredMixin, views.MethodView):
    permission_required = 'tasks-log-playbook'

    def get(self, pk):
        instance = db.session.query(PlaybookExecuteLog).get(pk)
        detail = {
            'id': instance.id,
            'content': instance.playbook
        }
        return render_template('tasks/realtime/playbook_file.html', **detail)
